package device

import (
	"context"
	"encoding/base64"
	"os"
	"strings"
	"time"

	"device-admin/config/mysql"
	"device-admin/config/redis"
	"device-admin/internal/app/grpc/client"
	"device-admin/internal/app/grpc/protoc/devicec"
	"device-admin/internal/app/grpc/protoc/robotc"
	"device-admin/internal/app/grpc/protoc/system_admin"
	"device-admin/internal/app/grpc/service/pubsub"
	"device-admin/internal/dal"
	"device-admin/internal/dao"
	"device-admin/internal/define"
	"device-admin/internal/models"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/helper"
	"github.com/samber/lo"
	"google.golang.org/protobuf/proto"
	"gorm.io/gorm"
)

// 终端客户端
type Terminal struct{}

// 同步终端列表
// 同步终端状态

type TerminalList struct {
	models.Terminal
	Online int `json:"online" gorm:"-"`
}

// VersionOption 版本下拉选项
func (Terminal) VersionOption(ctx context.Context, terminalType string) []string {
	return dao.Terminal{}.VersionOption(ctx, dal.Q, terminalType)
}

// ClientList 客户端列表
// 设备id，终端类型，在线状态，升级包下载进度，下载结果，升级结果
func (Terminal) List(
	ctx context.Context,
	loginUserMerchantID,
	loginUserMerchantPath string,
	robotID,
	terminalType string,
	online int,
	version string, // 软件版本
	provinceID, cityID, districtID string, //省市区id，多个以逗号分隔
	page, limit int,
) (
	list []dao.TerminalList, total int64, err error,
) {
	// 获取终端列表
	list, total, err = dao.Terminal{}.List(
		ctx, dal.Q,
		loginUserMerchantID, loginUserMerchantPath,
		robotID, terminalType, online, version, provinceID, cityID, districtID, page, limit,
	)

	// 商户id和区域id集合
	merchantIDs := []string{}
	areaIDs := []string{}
	locationIDs := []string{}
	for _, record := range list {
		merchantIDs = append(merchantIDs, record.MerchantID)
		areaIDs = append(areaIDs, record.ProvinceID, record.CityID, record.DistrictID)
		locationIDs = append(locationIDs, record.LocationID)
	}
	// 获取商户名称
	{
		cli, err := client.GetSystemAdminClient()
		if err == nil {
			rsp, err := cli.MerchantName(context.Background(), &system_admin.MerchantNameReq{MerchantIds: merchantIDs})
			if err == nil && rsp != nil {
				for index, record := range list {
					list[index].Merchant = rsp.MerchantNameMap[record.MerchantID]
				}
			}
		}
	}
	// 获取省市区的名称
	{
		cli, err := client.GetSystemAdminClient()
		if err == nil {
			rsp, err := cli.AreaName(context.Background(), &system_admin.AreaNameReq{AreaIds: areaIDs})
			if err == nil && rsp != nil {
				for index, record := range list {
					list[index].Province = rsp.AreaNameMap[record.ProvinceID]
					list[index].City = rsp.AreaNameMap[record.CityID]
					list[index].District = rsp.AreaNameMap[record.DistrictID]
				}
			}
		}
	}
	// 获取位置名称
	{
		locationMap := dao.Location{}.LocationNameMap(context.Background(), locationIDs)
		if locationMap != nil {
			for index, record := range list {
				list[index].Location = locationMap[record.LocationID]
			}
		}
	}
	return list, total, err
}

// UpdateStatus 更新状态
// statusType-online、upgradeFileDownloadProgress,upgradeFileDownloadResult,upgradeResult
func (Terminal) UpdateStatus(robotID, terminalType, statusType string, val interface{}) error {
	rowsAffected := mysql.NewDB().Model(&models.Terminal{}).
		Where("robot_id = ?", robotID).
		Where("terminal_type = ?", terminalType).
		Update(statusType, val).RowsAffected
	if rowsAffected <= 0 {
		return errorx.New("更新状态失败", -1)
	}
	return nil
}

type UpgradeVersion struct{}

type VersionPackage struct {
	ID           string `json:"id"`
	Version      string `json:"version"`
	TerminalType string `json:"terminal_type"`
	ChangeLog    string `json:"change_log"`
	ArchPkgs     []*models.UpgradePackage
}

// Detail 软件详情
func (UpgradeVersion) Detail(ctx context.Context, ID string) (*VersionPackage, error) {
	// 获取软件版本详情
	versionDetail, err := dao.UpgradeVersion{}.Detail(ctx, dal.Use(mysql.NewDB()), ID)
	if versionDetail == nil {
		return nil, err
	}
	// 获取软件版本对应的软件包
	_, pkglist, err := dao.UpgradePackage{}.List(
		ctx,
		dal.Use(mysql.NewDB()),
		versionDetail.Version,
		versionDetail.TerminalType,
		-1,
		"",
		"",
		-1,
		-1,
	)
	if len(pkglist) <= 0 {
		return nil, err
	}

	var versionPackage = &VersionPackage{}
	versionPackage.ID = versionDetail.ID
	versionPackage.Version = versionDetail.Version
	versionPackage.TerminalType = versionDetail.TerminalType
	versionPackage.ChangeLog = versionDetail.ChangeLog
	versionPackage.ArchPkgs = pkglist
	return versionPackage, nil
}

// List 版本列表
func (UpgradeVersion) List(
	ctx context.Context,
	version, terminalType string,
	page, limit int,
) (
	total int64,
	record []*models.UpgradeVersion,
	err error,
) {
	return dao.UpgradeVersion{}.List(
		ctx,
		dal.Use(mysql.NewDB()),
		version,
		terminalType,
		page,
		limit,
	)
}

// Add 新增软件版本
func (UpgradeVersion) Add(
	ctx context.Context,
	version, terminalType, changeLog string,
	archPkgs []ArchPkg,
) (string, error) {
	if len(archPkgs) <= 0 {
		return "", errorx.New("软件包不能为空！")
	}
	// 新的版本，一个软件版本对应多个软件包[分不同的架构]
	// 版本检查
	if version == "" {
		return "", errorx.New("版本不能为空", -1)
	}
	// 检查终端类型
	if !lo.Contains([]string{"95", "97", "98"}, terminalType) {
		return "", errorx.New("无效的终端类型", -1)
	}
	// 保存版本信息
	dao.UpgradeVersion{}.Create(ctx, dal.Use(mysql.NewDB()), models.UpgradeVersion{
		Version:      version,
		TerminalType: terminalType,
		ChangeLog:    changeLog,
	})
	// 保存软件包
	if len(archPkgs) > 0 {
		for _, archPkg := range archPkgs {
			var savefileRes *system_admin.ConfirmFileRsp
			var fileMd5 string
			// 文件确认
			{
				systemAdmin, err := client.GetSystemAdminClient()
				if err != nil {
					return "", errorx.New("文件系统出错", -1)
				}
				savefileRes, _ = systemAdmin.ConfirmFile(context.Background(), &system_admin.ConfirmFileReq{FileUrl: archPkg.FilePath})
				if savefileRes.Code != 0 {
					return "", errorx.New("文件系统出错", -1)
				}
				// 计算文件md5
				fileMd5 = helper.Md5File(savefileRes.FileUrl)
				if fileMd5 == "" {
					return "", errorx.New("升级包文件有误")
				}
			}
			// 保存软件包
			dao.UpgradePackage{}.Create(
				ctx,
				dal.Use(mysql.NewDB()),
				[]*models.UpgradePackage{
					{
						Version:      version,
						TerminalType: terminalType,
						Arch:         archPkg.Arch,
						FilePath:     savefileRes.FileUrl,
						FileMD5:      fileMd5,
					},
				},
			)
		}
	}
	return "", nil
}

// Update 更新软件版本
func (UpgradeVersion) Update(
	ctx context.Context,
	ID, version, terminalType, changLog string,
	archPkgs []ArchPkg,
) error {
	// 更新版本信息
	dao.UpgradeVersion{}.Update(ctx, dal.Use(mysql.NewDB()), models.UpgradeVersion{
		ID:           ID,
		Version:      version,
		TerminalType: terminalType,
		ChangeLog:    changLog,
	})
	// 获取版本详情，获取版本的包列表
	versionDetail, err := UpgradeVersion{}.Detail(ctx, ID)
	if versionDetail == nil {
		return err
	}
	var oldArchPkgs []ArchPkg
	for _, pkg := range versionDetail.ArchPkgs {
		oldArchPkgs = append(oldArchPkgs, ArchPkg{
			FilePath: pkg.FilePath,
			FileMd5:  pkg.FileMD5,
		})
	}
	// 新的文件路径
	for idx, pkg := range archPkgs {
		// 文件确认2
		if strings.Contains(pkg.FilePath, "/temp/") {
			var savefileRes *system_admin.ConfirmFileRsp
			var fileMd5 string
			// 文件确认
			{
				systemAdmin, err := client.GetSystemAdminClient()
				if err != nil {
					return errorx.New("文件系统出错", -1)
				}
				savefileRes, _ = systemAdmin.ConfirmFile(context.Background(), &system_admin.ConfirmFileReq{FileUrl: pkg.FilePath})
				if savefileRes.Code != 0 {
					return errorx.New("文件系统出错", -1)
				}
				// 计算文件md5
				fileMd5 = helper.Md5File(savefileRes.FileUrl)
				if fileMd5 == "" {
					return errorx.New("升级包文件有误")
				}
			}
			archPkgs[idx].FilePath = savefileRes.FileUrl
			archPkgs[idx].FileMd5 = fileMd5
			pkg.FilePath = savefileRes.FileUrl
			// pkg.FileMd5 = archPkgs[idx].FileMd5
		}
	}
	// 对比差异
	var addRecords = []*models.UpgradePackage{}
	adds, dels := lo.Difference(archPkgs, oldArchPkgs)
	// 新增的软件包
	for _, add := range adds {
		addRecords = append(addRecords, &models.UpgradePackage{
			Version:      version,
			TerminalType: terminalType,
			Arch:         add.Arch,
			FilePath:     add.FilePath,
			FileMD5:      add.FileMd5,
		})
	}
	dao.UpgradePackage{}.Create(
		ctx,
		dal.Use(mysql.NewDB()),
		addRecords,
	)
	// 要删除的软件包
	for _, del := range dels {
		_, records, _ := dao.UpgradePackage{}.List(ctx, dal.Use(mysql.NewDB()), "", "", -1, "", del.FileMd5, -1, 1)
		if len(records) == 1 {
			dao.UpgradePackage{}.Delete(
				ctx, dal.Use(mysql.NewDB()), records[0].ID,
			)
		}
	}
	return nil
}

// Delete 删除软件版本
func (UpgradeVersion) Delete(ctx context.Context, ID string) error {
	return dao.UpgradeVersion{}.Delete(ctx, dal.Use(mysql.NewDB()), ID)
}

// 升级包
type UpgradePackage struct{}

// List 升级包列表
func (UpgradePackage) List(terminalType string, page, limit int) (list []models.UpgradePackage, total int64, err error) {
	db := mysql.NewDB()
	tx := db.Model(&models.UpgradePackage{})
	if terminalType != "" {
		tx = tx.Where("terminal_type = ?", terminalType)
	}
	// 获取总条数
	tx.Count(&total)
	// 获取分页数据
	helper.Gorm{}.Paginate(tx, page, limit).Order("created_at desc").Find(&list)
	return list, total, nil
}

// 架构包
type ArchPkg struct {
	Arch     int    `json:"arch"`      // 软件包架构
	FilePath string `json:"file_path"` // 软件包存储路径
	FileMd5  string `json:"file_md5"`  // 软件包md5文件检验值
}

// AddPackage 新增升级包
func (UpgradePackage) AddPackage(
	ctx context.Context,
	version, terminalType, filePath, changeLog string,
) (string, error) {
	// 就的版本，一个软件版本对应一个软件包
	db := mysql.NewDB()
	if version == "" {
		return "", errorx.New("升级包版本不能为空", -1)
	}
	if filePath == "" {
		return "", errorx.New("升级包不能为空", -1)
	}

	// 判断版本是否已经存在
	var count int64
	db.Model(&models.UpgradePackage{}).
		Where("terminal_type = ?", terminalType).
		Where("version = ?", version).
		Count(&count)
	if count > 0 {
		return "", errorx.New("升级版本已存在", -1)
	}

	var upgradePackage models.UpgradePackage
	upgradePackage.Version = version
	upgradePackage.TerminalType = terminalType
	// 文件确认
	systemAdmin, err := client.GetSystemAdminClient()
	if err != nil {
		return "", errorx.New("系统出错", -1)
	}
	res, _ := systemAdmin.ConfirmFile(context.Background(), &system_admin.ConfirmFileReq{FileUrl: filePath})
	if res.Code != 0 {
		return "", errorx.New("系统出错", -1)
	}
	// 计算文件md5
	md5 := helper.Md5File(res.FileUrl)
	if md5 == "" {
		return "", errorx.New("升级包文件有误")
	}
	upgradePackage.FilePath = res.FileUrl
	upgradePackage.FileMD5 = md5
	upgradePackage.ChangeLog = changeLog
	rowsAffected := db.Create(&upgradePackage).RowsAffected
	if rowsAffected == 1 {
		return upgradePackage.ID, nil
	}
	return "", nil
}

// UpdatePackage 更新升级包
func (UpgradePackage) UpdatePackage(ID, version, terminalType, filePath, changeLog string) error {
	db := mysql.NewDB()
	// 判断id是否有效
	var upgradePackage models.UpgradePackage
	rowsAffected := db.Model(&models.UpgradePackage{}).Where("id = ?", ID).Take(&upgradePackage).RowsAffected
	if rowsAffected == 0 {
		return errorx.New("升级包不存在", -1)
	}

	// 终端类型检查
	if terminalType == "" {
		return errorx.New("终端类型不能为空", -1)
	}
	if !lo.Contains([]string{"95", "97", "98"}, terminalType) {
		return errorx.New("不支持的客户端类型", -1)
	}
	upgradePackage.TerminalType = terminalType

	// 版本号
	if version != "" {
		// 判断版本是否已经存在
		var packageRecord models.UpgradePackage
		rowsAffected := db.Model(&models.UpgradePackage{}).
			Where("terminal_type = ?", terminalType).
			Where("version = ?", version).
			Take(&packageRecord).RowsAffected
		if rowsAffected > 0 && packageRecord.ID != ID {
			return errorx.New("升级版本已存在", -1)
		}
		upgradePackage.Version = version
	}
	// 如果file有改变，删除旧的file，并重新计算md5
	if filePath != "" {
		md5 := helper.Md5File(filePath)
		if md5 == "" {
			return errorx.New("升级包文件有误", -1)
		}
		if md5 != upgradePackage.FileMD5 {
			// 文件确认
			systemAdmin, err := client.GetSystemAdminClient()
			if err != nil {
				return errorx.New("系统出错", -1)
			}
			res, _ := systemAdmin.ConfirmFile(context.Background(), &system_admin.ConfirmFileReq{FileUrl: filePath})
			if res.Code != 0 {
				return errorx.New("系统出错", -1)
			}
			// 删除旧的升级包
			os.Remove(upgradePackage.FilePath)
			upgradePackage.FilePath = res.FileUrl
			upgradePackage.FileMD5 = helper.Md5File(res.FileUrl)
		}
	}
	upgradePackage.ChangeLog = changeLog
	// 更新
	rowsAffected = db.Model(&models.UpgradePackage{}).Where("id = ?", ID).Updates(&upgradePackage).RowsAffected
	if rowsAffected == 0 {
		return errorx.New("更新失败", -1)
	}
	return nil
}

// DeletePackage 删除升级包
func (UpgradePackage) DeletePackage(ID string) error {
	db := mysql.NewDB()
	// 判断id是否有效
	var upgradePackage models.UpgradePackage
	rowsAffected := db.Model(&models.UpgradePackage{}).Where("id = ?", ID).Take(&upgradePackage).RowsAffected
	if rowsAffected == 0 {
		return errorx.New("升级包不存在", -1)
	}
	// 删除文件
	os.Remove(upgradePackage.FilePath)
	// 删除package记录
	rowsAffected = db.Model(&models.UpgradePackage{}).Where("id = ?", ID).Delete(&models.UpgradePackage{}).RowsAffected
	if rowsAffected == 0 {
		return errorx.New("删除失败", -1)
	}
	return nil
}

// 升级计划
type UpgradeSchedule struct{}

type UpgradeScheduleDetail struct {
	models.UpgradeSchedule
	IncludeRobotIDs string `json:"include_robot_ids" gorm:"column:include_robot_ids"`
}

// Detail 升级计划详情
func (UpgradeSchedule) Detail(ID string) (detail UpgradeScheduleDetail, err error) {
	db := mysql.NewDB()
	rowsAffected := db.Model(&models.UpgradeSchedule{}).
		Select(
			"upgrade_schedule.*",
			"GROUP_CONCAT(DISTINCT upgrade_terminals.robot_id) as include_robot_ids",
		).
		Joins("LEFT JOIN upgrade_terminals ON upgrade_terminals.schedule_id = upgrade_schedule.id").
		Where("upgrade_schedule.id = ?", ID).
		Take(&detail).RowsAffected
	if rowsAffected == 0 {
		return UpgradeScheduleDetail{}, errorx.New("升级计划不存在", -1)
	}
	return detail, nil
}

// List 升级计划列表
func (UpgradeSchedule) List(terminalType string, page, limit int) (list []models.UpgradeSchedule, total int64, err error) {
	db := mysql.NewDB()

	tx := db.Model(&models.UpgradeSchedule{})

	if terminalType != "" {
		tx.Where("terminal_type = ?", terminalType)
	}

	tx.Count(&total)

	tx = helper.Gorm{}.Paginate(tx, page, limit)
	tx.Select(
		"id",
		"name",
		"terminal_type",
		"force_upgrade",
		"start_time",
		"end_time",
		"created_at",
		"upgrade_version",
		"mode",
		"auto_upgrade", // 是否为自动更新
	).Order("created_at desc").Find(&list)
	return list, total, nil
}

// AddSchedule 新增计划
func (UpgradeSchedule) AddSchedule(
	ctx context.Context,
	userID string,
	terminalType,
	upgradeVersion,
	includeRobotIDs string,
	startTime, endTime, forceUpgrade, mode, autoUpgrade int64,
	name string,
) (string, error) {
	var upgradeSchedule models.UpgradeSchedule
	db := mysql.NewDB()
	rdb := redis.NewDB()
	success, _ := rdb.SetNX(ctx, define.RedisOperationLock+"AddSchedule:"+userID, 1, time.Second*3).Result()
	if !success {
		return "", errorx.New("操作太频繁", -1)
	}
	// 时间判断
	if startTime > endTime {
		return "", errorx.New("时间段不合法", -1)
	}

	if endTime < time.Now().Unix() {
		return "", errorx.New("结束时间不能晚于当前时间", -1)
	}

	if terminalType == "" {
		return "", errorx.New("终端类型不能为空", -1)
	}

	// 校验终端类型的合法性
	if !lo.Contains([]string{"95", "97", "98"}, terminalType) {
		return "", errorx.New("终端类型不合法", -1)
	}

	// 查询所有的设备
	var robotIDs []string
	db.Model(&models.Device{}).Distinct().Pluck("robot_id", &robotIDs)

	var validRobotIDs []string
	// 包含的设备
	if includeRobotIDs != "" {
		validRobotIDs = lo.Intersect[string](strings.Split(includeRobotIDs, ","), robotIDs)
	}
	// 判断版本是否存在
	var count int64
	db.Model(&models.UpgradePackage{}).
		Where("terminal_type = ?", terminalType).
		Where("version = ?", upgradeVersion).
		Count(&count)
	if count <= 0 {
		return "", errorx.New("升级包不存在", -1)
	}
	// 终端类型
	upgradeSchedule.TerminalType = terminalType
	// 计划的模式
	upgradeSchedule.Mode = mode
	// 目前升级版本
	upgradeSchedule.UpgradeVersion = upgradeVersion
	// 开始时间
	upgradeSchedule.StartTime = startTime
	// 结束时间
	upgradeSchedule.EndTime = endTime
	// 强制升级
	upgradeSchedule.ForceUpgrade = forceUpgrade
	// 自动安装or手动安装
	upgradeSchedule.AutoUpgrade = autoUpgrade
	// 升级计划名称
	upgradeSchedule.Name = name
	// 创建升级计划，并创建计划关联的终端列表
	rowsAffected := db.Model(&models.UpgradeSchedule{}).Create(&upgradeSchedule).RowsAffected
	if rowsAffected == 1 {
		// 新增设备升级计划关联
		upgradeTerminals := []models.UpgradeTerminals{}
		for _, robotID := range validRobotIDs {
			// 修改原计划的状态为取消升级
			db.Model(&models.UpgradeTerminals{}).
				Where("robot_id = ?", robotID).
				Where("terminal_type = ?", terminalType).
				Updates(map[string]interface{}{
					"upgrade_status": 7,
				})
			// 重置终端列表的升级状态
			db.Model(&models.Terminal{}).
				Where("robot_id = ?", robotID).
				Where("terminal_type = ?", terminalType).
				Updates(map[string]interface{}{
					"upgrade_status": 0,
				})
			upgradeTerminals = append(upgradeTerminals, models.UpgradeTerminals{
				ScheduleID:     upgradeSchedule.ID,
				ForceUpgrade:   upgradeSchedule.ForceUpgrade,
				RobotID:        robotID,
				TerminalType:   terminalType,
				UpgradeVersion: upgradeVersion,
			})
		}
		if len(upgradeTerminals) > 0 {
			db.Model(&models.UpgradeTerminals{}).CreateInBatches(&upgradeTerminals, 500)
		}
		return upgradeSchedule.ID, nil
	}
	return "", errorx.New("新增失败", -1)
}

// AddSchedule 更新计划
func (UpgradeSchedule) UpdateSchedule(
	ctx context.Context,
	userID string,
	ID, terminalType, upgradeVersion, includeRobotIDs string,
	startTime, endTime, forceUpgrade, mode, autoUpgrade int64,
	name string,
) error {
	var upgradeSchedule models.UpgradeSchedule
	db := mysql.NewDB()
	rdb := redis.NewDB()
	success, _ := rdb.SetNX(ctx, define.RedisOperationLock+"AddSchedule:"+userID, 1, time.Second*3).Result()
	if !success {
		return errorx.New("操作太频繁", -1)
	}
	rowsAffected := db.Model(&models.UpgradeSchedule{}).Where("id = ?", ID).Take(&upgradeSchedule).RowsAffected
	if rowsAffected == 0 {
		return errorx.New("计划不存在", -1)
	}

	// 原计划包含的设备id
	var oldTerminalRobotIDs []string
	db.Model(&models.UpgradeTerminals{}).Where("schedule_id = ?", ID).Distinct().Pluck("robot_id", &oldTerminalRobotIDs)

	// 计算要添加的终端及要取消的终端
	var addedTerminalRobotIDs, deletedTerminalRobotIDs []string
	{
		// 查询所有的设备
		var allRobotIDs []string
		db.Model(&models.Device{}).Distinct().Pluck("robot_id", &allRobotIDs)
		// 包含的设备
		var robotIDArr []string
		if includeRobotIDs != "" {
			robotIDArr = lo.Intersect[string](strings.Split(includeRobotIDs, ","), allRobotIDs)
		}
		deletedTerminalRobotIDs, addedTerminalRobotIDs = lo.Difference(oldTerminalRobotIDs, robotIDArr)
	}

	// 修改其它计划的状态为取消升级
	{
		for _, robotID := range oldTerminalRobotIDs {
			db.Model(&models.UpgradeTerminals{}).
				Where("robot_id = ?", robotID).
				Where("terminal_type = ?", terminalType).
				Where("schedule_id <> ?", ID).
				Updates(map[string]interface{}{
					"upgrade_status": 7,
				})
		}
	}

	// 删除要取消的终端
	db.Model(&models.UpgradeTerminals{}).
		Where("schedule_id = ?", ID).
		Where("robot_id IN ?", deletedTerminalRobotIDs).
		Delete(&models.UpgradeTerminals{})

	// 时间判断
	if startTime != 0 && endTime != 0 {
		if startTime > endTime {
			return errorx.New("时间段不合法", -1)
		}
	}

	if terminalType == "" {
		return errorx.New("终端类型不能为空", -1)
	}

	// 校验终端类型的合法性
	if terminalType != "" {
		if !lo.Contains([]string{"95", "97", "98"}, terminalType) {
			return errorx.New("终端类型不合法", -1)
		}
	}

	// 判断版本是否存在
	if upgradeVersion != "" {
		var count int64
		db.Model(&models.UpgradePackage{}).
			Where("terminal_type = ?", terminalType).
			Where("version = ?", upgradeVersion).
			Count(&count)
		if count <= 0 {
			return errorx.New("升级包不存在", -1)
		}
	}

	// 终端类型有更新
	if len(addedTerminalRobotIDs) > 0 {
		// 新增设备升级计划关联
		upgradeTerminals := []models.UpgradeTerminals{}
		for _, robotID := range addedTerminalRobotIDs {
			// 修改其它计划的状态为取消升级
			db.Model(&models.UpgradeTerminals{}).
				Where("robot_id = ?", robotID).
				Where("terminal_type = ?", terminalType).
				Updates(map[string]interface{}{
					"upgrade_status": 7,
				})
			// 待升级的终端
			upgradeTerminals = append(upgradeTerminals, models.UpgradeTerminals{
				ScheduleID:     upgradeSchedule.ID,
				ForceUpgrade:   upgradeSchedule.ForceUpgrade,
				RobotID:        robotID,
				TerminalType:   terminalType,
				UpgradeVersion: upgradeVersion,
				CreatedAt:      upgradeSchedule.CreatedAt, // 同步于计划的时间
			})
			// 重置终端列表的升级状态
			db.Model(&models.Terminal{}).
				Where("robot_id = ?", robotID).
				Where("terminal_type = ?", terminalType).
				Updates(map[string]interface{}{
					"upgrade_status": 0,
				})
		}
		if len(upgradeTerminals) > 0 {
			db.Model(&models.UpgradeTerminals{}).CreateInBatches(&upgradeTerminals, 500)
		}
	}
	upgradeSchedule.TerminalType = terminalType
	// 计划的模式
	upgradeSchedule.Mode = mode
	// 目标版本
	upgradeSchedule.UpgradeVersion = upgradeVersion
	// 开始时间
	upgradeSchedule.StartTime = startTime
	// 结束时间
	upgradeSchedule.EndTime = endTime
	// 强制更新标记
	upgradeSchedule.ForceUpgrade = forceUpgrade
	// 自动安装or手动安装
	upgradeSchedule.AutoUpgrade = autoUpgrade
	// 升级计划的名称
	upgradeSchedule.Name = name
	// 更新终端的下载方式和升级版本
	db.Model(&models.UpgradeTerminals{}).Where(&models.UpgradeTerminals{
		ScheduleID: ID,
	}).Updates(map[string]interface{}{
		"force_upgrade":   forceUpgrade,
		"upgrade_version": upgradeVersion,
	})
	// 更新
	rowsAffected = db.Model(&models.UpgradeSchedule{}).
		Where("id = ?", ID).
		Updates(&upgradeSchedule).RowsAffected
	if rowsAffected == 1 {
		return nil
	}
	return errorx.New("更新失败", -1)
}

// DeleteSchedule 删除计划
func (UpgradeSchedule) DeleteSchedule(ID string) error {
	db := mysql.NewDB()
	var upgradeSchedule models.UpgradeSchedule
	rowsAffected := db.Model(&models.UpgradeSchedule{}).
		Where("id = ?", ID).
		Delete(&upgradeSchedule).RowsAffected
	if rowsAffected == 1 {
		db.Where("schedule_id = ?", ID).Delete(&models.UpgradeTerminals{})
		return nil
	}
	return errorx.New("删除失败", -1)
}

// UpgradeTerminals 终端列表
func (UpgradeSchedule) UpgradeTerminals(ID, terminalType, robotID string, page, limit int) (list []models.UpgradeTerminals, total, success int64, err error) {
	db := mysql.NewDB()

	if ID == "" {
		return nil, 0, 0, errorx.New("所属计划不能为空", -1)
	}
	tx := db.Model(&models.UpgradeTerminals{}).Where("schedule_id = ?", ID)

	if terminalType != "" {
		tx = tx.Where("terminal_type = ?", terminalType)
	}
	if robotID != "" {
		tx = tx.Where("robot_id = ?", robotID)
	}
	tx = tx.Session(&gorm.Session{})

	// 计算总数
	tx.Count(&total)

	tx.Where("upgrade_status = 5").Count(&success)

	// 获取分页数据
	helper.Gorm{}.Paginate(tx, page, limit).
		Order("robot_id asc").
		Find(&list)

	return list, total, success, nil
}

// StartManualUpgrade 开始安装
func (UpgradeSchedule) StartManualUpgrade(terminalType, robotID string) error {
	if robotID == "" {
		return errorx.New("设备id不能为空", -1)
	}
	reqManulUpgrade := &robotc.ReqManualUpgrade{}
	reqManulUpgradeName := string(reqManulUpgrade.ProtoReflect().Descriptor().FullName())
	reqManulUpgradeBytes, _ := proto.Marshal(reqManulUpgrade)
	if terminalType == "95" {
		wm := &robotc.WildMsg{}
		wm.BodyType = reqManulUpgradeName
		wm.StrGUID = helper.RandString(16)
		wm.FromID = "server"
		wm.ToID = "95"
		wm.BodyContent = reqManulUpgradeBytes
		bytes, _ := proto.Marshal(wm)
		pubsub.CMDPublish(robotID, terminalType, "wildmsg", base64.StdEncoding.EncodeToString(bytes))
		return nil
	} else {
		m := &devicec.Msg{}
		m.Name = reqManulUpgradeName
		m.Content = reqManulUpgradeBytes
		m.Seq = helper.RandString(16)
		bytes, _ := proto.Marshal(m)
		pubsub.CMDPublish(robotID, terminalType, "msg", base64.StdEncoding.EncodeToString(bytes))
		return nil
	}
}

// BatchInstall 批量安装
func (UpgradeSchedule) BatchInstall(ctx context.Context, scheduleID string) error {
	if scheduleID == "" {
		return errorx.New("计划id不能为空", -1)
	}
	list, err := dao.UpgradeTerminals{}.List(
		ctx,
		dal.Q,
		scheduleID,
		"",
		"",
		"",
		4,
		-1, -1,
	)
	if err != nil {
		return errorx.New("没有可安装的终端", -1)
	}
	if len(list) > 0 {
		for _, record := range list {
			UpgradeSchedule{}.StartManualUpgrade(record.TerminalType, record.RobotID)
		}
		return nil
	} else {
		return errorx.New("没有可安装的终端", -1)
	}
}
